// axios 封装学习
import axios, { AxiosResponse } from 'axios';
import type { AxiosInstance, AxiosRequestConfig } from 'axios';
import type { RequestConfig, RequestInterceptors, CancelRequestSource } from './types';

class Request {
    // 实例
    instance: AxiosInstance
    // 实例的拦截器
    interceptorsObj: RequestInterceptors
    /**
     * 存放取消方法的集合
     * 在创建请求后将取消请求方法 push 到该集合中
     * 封装一个方法，可以取消请求，传入 url: string|string[]
     * 在请求之前判断同一URL是否存在，如果存在就取消请求
     */
    cancelRequestSourceList?: CancelRequestSource[]
    /**
     * 存放所有请求URL的集合
     * 存放所有请求URL的集合
     * 请求之前需要将url push到该集合中
     * 添加在发送请求之前完成，删除在响应之后删除
     */
    requestUrlList?: string[]

    constructor(config: RequestConfig) {
        this.instance = axios.create(config)
        // 实例拦截器
        this.interceptorsObj = config.interceptiors;
        // 初始化请求集合和取消请求的集合
        this.cancelRequestSourceList = [];
        this.requestUrlList = [];


        // 添加类请求拦截器
        this.instance.interceptors.request.use((res: AxiosRequestConfig) => {
            console.log('全局请求拦截器');
            return res;
        }, (err: any) => err)


        // 实例的请求拦截器
        this.instance.interceptors.request.use(
            this.interceptorsObj?.requestInterceptors,
            this.interceptorsObj?.requestInterceptorsCatch
        )
        // 实例的响应拦截器
        this.instance.interceptors.response.use(
            this.interceptorsObj?.responseInterceptors,
            this.interceptorsObj?.responseInterceptorsCatch
        )
        // 添加类响应拦截器---确保最后执行
        // 观看源码可知，请求拦截器是先加入的后执行，因为用的是unshift。而响应拦截器是用的push，所以是先加入的先执行
        // 拦截器的执行顺序为实例请求→类请求→实例响应→类响应
        this.instance.interceptors.response.use((res: AxiosResponse) => {
            console.log('全局响应拦截器');
            return res.data;
        }, (err: any) => err)
    }

    /**
     * 因为我们在全局的响应拦截器直接返回了data中的数据，所以返回的类型，已经不是 AxiosResponse 了
     * 我们查看request的类型定义如下：request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
     * 所以我们 可以指定返回值的类型
     * @param config 
     * @returns 
     */
    request<T>(config: RequestConfig): Promise<T> {
        return new Promise((resolve, reject) => {
            // 如果我们指定了单个请求的拦截器，那么这里使用单个请求的拦截器
            if (config.interceptiors?.requestInterceptors) {
                // 直接执行了一下， 拿到处理后的config
                config = config.interceptiors.requestInterceptors(config);
            }

            const url = config.url;
            if (url) {
                // 请求开始，将URL存入
                this.requestUrlList.push(url);
                config.cancelToken = new axios.CancelToken(c => {
                    this.cancelRequestSourceList?.push({
                        [url]: c
                    })
                })
            }
            this.instance
                .request<any, T>(config)
                .then(res => {
                    // 如果使用了接口响应拦截器，就在这使用响应拦截器
                    if (config.interceptiors?.responseInterceptors) {
                        res = config.interceptiors.responseInterceptors<T>(res);
                    }
                    resolve(res);
                }).catch(err => {
                    reject(err);
                }).finally(() => {
                    // 请求结束，删除请求URL
                    url && this.delUrl(url);
                })
        })
    }

    /**
     * 获取目标地址的cancel Token 的索引
     * @param url 请求地址
     */
    private getSourceIndex(url: String): number {
        return this.cancelRequestSourceList.findIndex((item: CancelRequestSource) => {
            // 因为对象的key是URL，所以只能用 Object.keys来做比较
            return Object.keys(item)[0] === url;
        }) as number;
    }
    /**
     * 删除请求地址
     * 两个集合中都要删除
     * @param url 请求地址
     */
    private delUrl(url: String): void {
        const urlIndex = this.requestUrlList.findIndex(u => u === url);
        const tokenIndex = this.getSourceIndex(url);
        // 删除
        urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1);
        tokenIndex !== -1 && this.cancelRequestSourceList?.splice(tokenIndex as number, 1);
    }

    /**
     * 取消全部请求
     * 只要把所有方法执行一遍即可
     */
    cancelAllRequest() {
        this.cancelRequestSourceList?.forEach(source => {
            const key = Object.keys(source)[0];
            source[key]("key");
        })
    }


    /**
     * 取消请求
     * @param url 取消请求URL
     */
    cancelRequest(url: string | string[]) {
        if (Array.isArray(url)) {
            // 存在多个需要取消的请求地址
            url.forEach(u => {
                const sourceIndex = this.getSourceIndex(u);
                sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u]("sourceIndex][u");
            })
        } else {
            // 取消单个请求
            const sourceIndex = this.getSourceIndex(url);
            sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url]("sourceIndex][url");
        }
    }
}

export default Request;
